# -*- coding: utf-8 -*-

__author__ = 'z417'
import functools
import importlib
import os
import time
from types import FunctionType, MethodType

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.touch_actions import TouchActions
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.ui import WebDriverWait
from webrunner.common import BASEDIR, L, to_Dict
from webrunner.common.exceptions import NotFoundElementError


class MyDriver():
    def __init__(self, driver: webdriver):
        """
        :param driver:
        """
        self.driver = driver
        self.driver.maximize_window()

    def sleep(self, s):
        time.sleep(s)

    def get(self, url, time_wait=3):
        """
        :param url: target url to test
        :param time_wait: after get the url
        """
        self.driver.get(url)
        if isinstance(time_wait, int):
            self.sleep(time_wait)

    def forward(self):
        """Browser forward operation"""
        self.driver.forward()

    def back(self):
        """Browser back operation"""
        self.driver.back()

    def quit(self):
        self.driver.quit()

    def refresh(self):
        """refresh web page """
        self.driver.refresh()

    def execute_script(self, js, *args):
        """
        execute js
        :param js: js code
        TODO: support js file
        """
        return self.driver.execute_script(js, *args)

    def _text(self, locator):
        return self._find_element(locator).text

    def implicitly_wait(self, seconds):
        """
        implicitly wait
        :param seconds: custom
        """
        self.driver.implicitly_wait(seconds)

    def get_screenshot_as_base64(self):
        return self.driver.get_screenshot_as_base64()

    def screen_shot(self, pic_name=None):
        """
        :param pic_name: custom, if None, like '20200117_112233.png'
        """
        if pic_name:
            rq = pic_name
        else:
            rq = time.strftime('%Y%m%d_%H%M%S', time.localtime(
                time.time())) + '.png'
        screen_name = os.path.join(BASEDIR, rq)
        try:
            self.driver.get_screenshot_as_file(screen_name)
        except NameError as e:
            L.warning(e)
            self.screen_shot()

    def _click(self, locator, count=1):
        """
        click action
        :param locator: locator info like {'name': 'search', 'type': 'name', 'value': 'q', 'time_out': 30}
        :param count: click times
        """
        el = self._find_element(locator)
        if count == 1:
            try:
                el.click()
            except Exception as e:
                L.warning(e)
                self.execute_script('arguments[0].click()', el)
        else:
            touch_action = TouchActions(self.driver)
            try:
                for x in range(count):
                    touch_action.tap(el).perform()
            except Exception as e:
                L.error(e)

    def _send_keys(self, locator, value, clear_first=False, click_first=True):
        """
        input text
        :param locator:
        :param value: the text input
        :param clear_first:
        :param click_first:
        """
        __ = self._find_element(locator)
        if click_first:
            try:
                __.click()
            except Exception as e:
                L.warning(e)
                self.execute_script('arguments[0].click()', __)
        if clear_first:
            __.clear()
        __.send_keys(value)

    def _get_attribute(self, locator, value2):
        """
        Gets the value of the element attribute
        :param locator:
        :param value2: which attribute you want, like 'text'
        :return:
        """
        Value = self._find_element(locator).get_attribute(value2)
        return Value

    def web_driver_wait(self, max_time, min_time, locator):
        """
        Explicitly wait time
        :param max_time:
        :param min_time:
        :param locator:
        """
        element = self._find_element(locator, is_need_displayed=False)
        WebDriverWait(self.driver, max_time,
                      min_time).until(EC.presence_of_element_located(element))

    def move_action(self, locator):
        """
        Mouse movement click event
        :param locator:
        """
        xm = self._find_element(locator)
        ActionChains(self.driver).click(xm).perform()

    def _is_selected(self, locator):
        """
        Verify that the button is checked
        :param locator:
        :return:
        """
        return self._find_element(locator).is_selected()

    def wait_4_page_load(self, duration=20):
        WebDriverWait(self.driver, duration).until(self._is_page_loaded)

    def _is_page_loaded(self, driver=None):
        res = self.driver.execute_script("return document.readyState")
        return res == "complete"

    def _find_element(self, locator, is_need_displayed=True):
        """
        find the element and returns the first if there are more than one
        :param locator:
        :param is_need_displayed: Elements that need to be located must be displayed
        :return: the element
        :raises: NotFoundElementError
        """
        if 'time_out' in locator:
            wait = locator['time_out']
        else:
            wait = 20
        try:
            if is_need_displayed:
                WebDriverWait(self.driver,
                              wait).until(lambda driver: self.get_element(locator).is_displayed())
            else:
                WebDriverWait(self.driver,
                              wait).until(lambda driver: self.get_element(locator) is not None)
            return self.get_element(locator)
        except Exception as e:
            L.error("[element] can not find %s cause %s" % (locator, e))
            raise NotFoundElementError

    def select_child_element(self, locator, child):
        """
        Get the child element
        :param locator:
        :param child:
        :return:
        """
        return Select(
            self._find_element(locator)).select_by_visible_text(child)

    def _find_elements(self, locator):
        """
        find elements and won't throw exceptions
        :param locator:
        :return: elements list
        """
        if 'time_out' in locator:
            wait = locator['time_out']
        else:
            wait = 20
        try:
            WebDriverWait(self.driver,
                          wait).until(lambda driver: self.get_element(locator, False).__len__() > 0)
            return self.get_element(locator, False)
        except NotFoundElementError:
            L.error("[elements] can not find %s" % locator)
            return []

    def ltype_value(self, locator):
        """
        extract locator as webdriver required.
        :param locator: locator info like {'type': 'name', 'value': 'q', 'time_out': 30}
        """
        ltype = locator['type']
        __swtich_case__ = {
            "id": By.ID,
            "xpath": By.XPATH,
            "name": By.NAME,
            "link_text": By.LINK_TEXT,
            "partial": By.PARTIAL_LINK_TEXT,
            "tag": By.TAG_NAME,
            "class": By.CLASS_NAME,
            "css": By.CSS_SELECTOR
        }
        try:
            ltype = __swtich_case__[ltype]
            L.info("find_element {}".format(locator))
            return ltype, locator['value']
        except KeyError as e:
            L.error('type {} not support'.format(e))
            raise e

    def get_element(self, locator, element=True):
        """
        :param element:
            True: Single element
            False: muilt elements
        :return: the element obj or list of elements obj
        """
        ltype, value = self.ltype_value(locator)
        return self.driver.find_element(
            ltype, value) if element else self.driver.find_elements(
            ltype, value)

    def __getattr__(self, attr) -> webdriver:
        """
        To support other attributes belongs to webdriver, but doesn't overwrite in MyDriver class
        """
        try:
            _attr = getattr(self.driver, attr)
            if callable(_attr) or isinstance(_attr, (FunctionType, MethodType)):
                @functools.wraps(_attr)
                def wrapper(*args, **kw):
                    return _attr(*args, **kw)
                return wrapper
            else:
                return _attr
        except AttributeError as e:
            raise AttributeError(e)

    @property
    def hotPlug(self):
        """prepare for cases selector and runner engine"""
        importedModule = importlib.import_module('hotPlug')
        module_functions = {}
        for name, item in vars(importedModule).items():
            if isinstance(item, FunctionType):
                module_functions[name] = item
        return to_Dict(module_functions)
